package org.octopus.data.desensitized.jackson.serializer;


import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import java.io.IOException;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.octopus.data.desensitized.core.annotation.DesensitizedRule;
import org.octopus.data.desensitized.core.registry.GlobalDesensitizedStrategyRegistry;
import org.octopus.data.desensitized.core.strategy.DesensitizedStrategy;
import org.octopus.data.desensitized.jackson.util.JacksonDesensitizedUtils;

/**
 * 数据脱敏
 */
@Slf4j
public class JacksonDesensitizedSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private final DesensitizedRule desensitizedRule;
    private final DesensitizedStrategy<String> desensitizedStrategy;

    public JacksonDesensitizedSerializer() {
        this.desensitizedRule = null;
        this.desensitizedStrategy = null;
    }

    public JacksonDesensitizedSerializer(DesensitizedRule desensitizedRule,
            DesensitizedStrategy<String> desensitizedStrategy) {
        this.desensitizedRule = desensitizedRule;
        this.desensitizedStrategy = desensitizedStrategy;
    }

    /**
     * Method that can be called to ask implementation to serialize
     * values of type this serializer handles.
     *
     * @param value         Value to serialize; can <b>not</b> be null.
     * @param jsonGenerator Generator used to output resulting Json content
     * @param serializers   Provider that can be used to get serializers for
     */
    @Override
    public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializers)
            throws IOException {
        if (StringUtils.isBlank(value)) {
            jsonGenerator.writeObject(value);
            return;
        }

        if (desensitizedStrategy == null) {
            log.debug("Not MaskStrategy");
            jsonGenerator.writeObject(value);
            return;
        }

        String newValue = this.desensitizedStrategy.convert(value, this.desensitizedRule);

        log.info("原始值[{}]脱敏后[{}]", value, newValue);

        jsonGenerator.writeString(newValue);
    }

    /**
     * Method called to see if a different (or differently configured) serializer
     * is needed to serialize values of specified property.
     * Note that instance that this method is called on is typically shared one and
     * as a result method should <b>NOT</b> modify this instance but rather construct
     * and return a new instance. This instance should only be returned as-is, in case
     * it is already suitable for use.
     *
     * @param prov     Serializer provider to use for accessing config, other serializers
     * @param property Method or field that represents the property
     *                 (and is used to access value to serialize).
     *                 Should be available; but there may be cases where caller cannot provide it and
     *                 null is passed instead (in which case impls usually pass 'this' serializer as is)
     * @return Serializer to use for serializing values of specified property;
     * may be this instance or a new instance.
     * @throws JsonMappingException
     */
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
            throws JsonMappingException {
        if (property == null || !Objects.equals(property.getType().getRawClass(), String.class)) {
            return prov.findNullValueSerializer(null);
        }

        DesensitizedRule desensitizedRule = JacksonDesensitizedUtils.obtainDesensitizedRule(property);

        if (desensitizedRule == null) {
            return prov.findValueSerializer(property.getType(), property);
        }

        if (!desensitizedRule.isEnable()) {
            if (log.isDebugEnabled()) {
                log.debug("字段[{}]上脱敏注解未启用：{}", property.getName(), desensitizedRule);
            }

            return prov.findValueSerializer(property.getType(), property);
        }

        DesensitizedStrategy<String> desensitizedStrategy = GlobalDesensitizedStrategyRegistry
                .obtainDesensitizedStrategy(String.class, desensitizedRule.getDesensitizedPolicy());

        if (desensitizedStrategy == null) {
            log.warn("字段[{}]未找到匹配的脱敏策略，脱敏注解：{}", property.getName(), desensitizedRule);
            return prov.findValueSerializer(property.getType(), property);
        } else {
            return new JacksonDesensitizedSerializer(desensitizedRule, desensitizedStrategy);
        }
    }
}
